package hdgem.bezier;

import hdgeom.primitives.AbstractShape;
import hdgeom.primitives.Plane;

import java.util.ArrayList;
import java.util.Collections;

import processing.core.PGraphics;
import processing.core.PMatrix3D;
import processing.core.PVector;
import simpleMesh.Face;

public class BezierFace extends AbstractShape {

	public ArrayList<Bezier> beziers = new ArrayList<Bezier>();

	public BezierFace() {

	}

	public BezierFace(Face face) {
		for (int ii = 0; ii < face.vertices.size(); ii++) {
			PVector v1 = face.vertices.get(ii);
			PVector v2 = face.vertices.get((ii + 1) % face.vertices.size());
			Bezier bezier = new Bezier(v1, v2);
			beziers.add(bezier);
		}
	}

	public BezierFace(PVector v1, PVector v2, PVector v3, PVector v4) {
		Bezier b1 = new Bezier();
		b1.addControlPoint(v1.get());
		b1.addControlPoint(v2.get());
		Bezier b2 = new Bezier();
		b2.addControlPoint(v2.get());
		b2.addControlPoint(v3.get());
		Bezier b3 = new Bezier();
		b3.addControlPoint(v3.get());
		b3.addControlPoint(v4.get());
		Bezier b4 = new Bezier();
		b4.addControlPoint(v4.get());
		b4.addControlPoint(v1.get());
		// create the bezier face
		beziers.add(b1);
		beziers.add(b2);
		beziers.add(b3);
		beziers.add(b4);
		alignOrientation();
	}

	public BezierFace(PVector v1, PVector v2, PVector v3) {
		Bezier b1 = new Bezier();
		b1.addControlPoint(v1.get());
		b1.addControlPoint(v2.get());
		Bezier b2 = new Bezier();
		b2.addControlPoint(v2.get());
		b2.addControlPoint(v3.get());
		Bezier b3 = new Bezier();
		b3.addControlPoint(v3.get());
		b3.addControlPoint(v1.get());

		// create the bezier face
		beziers.add(b1);
		beziers.add(b2);
		beziers.add(b3);

		alignOrientation();
	}

	public void alignOrientation() {
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b1 = beziers.get(i);
			Bezier b2 = beziers.get((i + 1) % beziers.size());
			PVector p1 = b1.controlPoints.get(b1.controlPoints.size() - 1);
			PVector p2 = b2.controlPoints.get(0);
			if (p1.dist(p2) > 0.1) {
				b1.inverse();
			}
		}
	}

	public void reverse() {
		Collections.reverse(beziers);
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b = beziers.get(i);
			// System.out.println("reverse");
			Collections.reverse(b.controlPoints);
		}
		rot();
	}

	public void offsetRelative(float offset) {
		PVector sum = new PVector();
		for (int i = 0; i < beziers.size(); i++) {
			sum.add(beziers.get(i).controlPoints.get(0));
		}
		sum.mult(1f / beziers.size());
		for (int i = 0; i < beziers.size(); i++) {
			Bezier bezier = beziers.get(i);
			for (int ii = 0; ii < bezier.controlPoints.size(); ii++) {
				PVector cP = bezier.controlPoints.get(ii);
				PVector p12 = PVector.sub(sum, cP);
				p12.mult(offset);
				cP.add(p12);
			}
		}
	}

	public BezierFace getCopy() {
		BezierFace face = new BezierFace();
		for (int i = 0; i < beziers.size(); i++) {
			face.beziers.add(beziers.get(i).getCopy());
		}
		return face;
	}

	public void rot() {
		beziers.add(0, beziers.get(beziers.size() - 1));
		beziers.remove(beziers.size() - 1);
	}

	public void display2D(PGraphics g, int resolution) {
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b = beziers.get(i);
			b.display2D(g, resolution);
		}
	}

	public void display3D(PGraphics g, int resolution) {
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b = beziers.get(i);
			b.display3D(g, resolution);
		}
	}

	public void display3DFill(PGraphics g, int resolution) {
		g.beginShape();
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b = beziers.get(i);
			for (int ii = 0; ii < resolution; ii++) {
				PVector p = b.getPoint(ii * 1f / resolution);
				g.vertex(p.x, p.y, p.z);
			}
		}
		g.endShape(PGraphics.CLOSE);
	}

	public Bezier getBezier(int i) {
		return beziers.get(i % beziers.size());
	}

	public PVector getNormal(int bezier, float t) {
		Bezier bez = getBezier(bezier);
		return bez.getNormal(t);
	}

	public PVector getNormal() {
		PVector normal = new PVector();
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b1 = beziers.get(i);
			Bezier b2 = beziers.get((i + 1) % beziers.size());
			PVector v0 = b1.controlPoints.get(0);
			PVector v1 = b2.controlPoints.get(0);
			PVector v2 = b2.controlPoints.get(b2.controlPoints.size() - 1);
			normal.add(AbstractShape.normal(v0, v1, v2));
		}
		normal.normalize();
		return normal;
	}

	public PVector getPoint(int bezier, float t) {
		Bezier bez = getBezier(bezier);
		return bez.getPoint(t);
	}

	public PVector getTangent(int bezier, float t) {
		Bezier bez = getBezier(bezier);
		return bez.getTangent(t);
	}

	public ArrayList<PVector> getTangentCorners(ArrayList<PVector> profile,
			int bezier) {
		ArrayList<PVector> newProfile = new ArrayList<PVector>();
		PVector tangent = getTangent(bezier, 0);
		PVector normal = this.getNormal();
		PMatrix3D pm = beziers.get(bezier).getPMatrixUp(0, normal);
		Plane plane = getTangentPlane(bezier);
		for (int i = 0; i < profile.size(); i++) {
			PVector p = profile.get(i);
			PVector p1 = new PVector();
			pm.mult(p, p1);
			PVector cutP = plane.linePlaneIntersection(p1, tangent);
			newProfile.add(cutP);
		}
		return newProfile;
	}
	public void displayExtrudeBeziers(PGraphics g,
			ArrayList<PVector> profile,int res) {
		PVector norm=this.getNormal();
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b=beziers.get(i);
			b.displayExtrudeUp(g, profile, res, true, norm);
		}
	}
	public void displayExtrudeBeziersSimple(PGraphics g,
			ArrayList<PVector> profile,int res) {
		
		for (int i = 0; i < beziers.size(); i++) {
			Bezier b=beziers.get(i);
			b.displayExtrude(g, profile, res, true);
		}
	}
	public void displayExtrudeBezierFrame(PGraphics g,
			ArrayList<PVector> profile) {
		for (int i = 0; i < beziers.size(); i++) {
			ArrayList<PVector> profile01 = getTangentCorners(profile, i);
			ArrayList<PVector> profile02 = getTangentCorners(profile, (i + 1)
					% beziers.size());
			g.beginShape(PGraphics.QUAD_STRIP);
			for (int ii = 0; ii < profile01.size() + 1; ii++) {
				int index = ii % profile01.size();
				PVector v1 = profile01.get(index);
				PVector v2 = profile02.get(index);

				g.vertex(v1.x, v1.y, v1.z);
				g.vertex(v2.x, v2.y, v2.z);

			}
			g.endShape();
		}
	}

	public void displayExtrudeFrame(PGraphics g, ArrayList<PVector> profile) {
		for (int i = 0; i < beziers.size(); i++) {
			ArrayList<PVector> profile01 = getTangentCorners(profile, i);
			ArrayList<PVector> profile02 = getTangentCorners(profile, (i + 1)
					% beziers.size());
			g.beginShape(PGraphics.QUAD_STRIP);
			for (int ii = 0; ii < profile01.size() + 1; ii++) {
				int index = ii % profile01.size();
				PVector v1 = profile01.get(index);
				PVector v2 = profile02.get(index);
				g.vertex(v1.x, v1.y, v1.z);
				g.vertex(v2.x, v2.y, v2.z);
			}
			g.endShape();
		}
	}

	public Plane getTangentPlane(int bezier) {
		Bezier b1 = beziers.get(bezier);
		int bezierPrev = bezier - 1;
		if (bezierPrev < 0)
			bezierPrev = beziers.size() - 1;
		Bezier b0 = beziers.get(bezierPrev);
		PVector o = getPoint(bezier, 0);
		PVector tangent0 = b0.getTangent(1);
		PVector tangent1 = b1.getTangent(0);
		PVector normal = PVector.add(tangent0, tangent1);
		normal.normalize();
		Plane plane = new Plane(o, normal);
		return plane;
	}

	public void split(int b1, float p1, int b2, float p2, BezierFace f1,
			BezierFace f2, Bezier connectBezier) {
		// tangent
		// orthogonal
		b1 = b1 % beziers.size();
		b2 = b2 % beziers.size();
		if (b1 == b2) {
			b2++;
			b2 = b2 % beziers.size();
		}
		int index1 = 0;
		int index2 = 0;
		ArrayList<Bezier> newBeziers = new ArrayList<Bezier>();

		Bezier bezier1 = beziers.get(b1);
		PVector pt1 = bezier1.getPoint(p1);
		Bezier bezier2 = beziers.get(b2);
		PVector pt2 = bezier2.getPoint(p2);

		connectBezier.addControlPoint(pt1);
		connectBezier.addControlPoint(pt2);

		for (int i = 0; i < beziers.size(); i++) {
			Bezier b = beziers.get(i);
			if (i == b1 || i == b2) {
				Bezier bez1 = new Bezier();
				Bezier bez2 = new Bezier();
				newBeziers.add(bez1);
				newBeziers.add(bez2);
				if (i == b1) {
					index1 = newBeziers.size() - 1;
					b.splitBezier(p1, bez1, bez2);
				} else if (i == b2) {
					index2 = newBeziers.size() - 1;
					b.splitBezier(p2, bez1, bez2);
				}
			} else {
				newBeziers.add(b);
			}
		}
		f1.beziers.add(connectBezier);
		int cIndex = index1;
		do {
			f1.beziers.add(newBeziers.get(cIndex));
			cIndex++;
			cIndex = cIndex % newBeziers.size();
		} while (cIndex != index2);

		f2.beziers.add(connectBezier);
		cIndex = index2;
		do {
			f2.beziers.add(newBeziers.get(cIndex));
			cIndex++;
			cIndex = cIndex % newBeziers.size();
		} while (cIndex != index1);

	}

	@Override
	public void transform(PMatrix3D matrix) {
		// TODO Auto-generated method stub
		for (int i = 0; i < beziers.size(); i++) {
			Bezier bezier = beziers.get(i);
			bezier.transform(matrix);
		}
	}

	@Override
	public int getNumVertices() {
		// TODO Auto-generated method stub
		int sumV=0;
		for (int i=0;i<beziers.size();i++){
			sumV+=beziers.size();
		}
		return sumV;
	}

	@Override
	public void getPosition(int i, float[] coords) {
		// TODO Auto-generated method stub
		int rI=i;
		for (int j=0;j<beziers.size();j++){
			Bezier b=beziers.get(j);
			if (rI<b.getNumVertices()){
				b.getPosition(rI, coords);
				return;
			}
			rI-=b.getNumVertices();
		}
	}

	@Override
	public void setPosition(int i, float x, float y, float z) {
		// TODO Auto-generated method stub
		int rI=i;
		for (int j=0;j<beziers.size();j++){
			Bezier b=beziers.get(j);
			if (rI<b.getNumVertices()){
				PVector p=b.controlPoints.get(rI);
				p.set(x,y,z);
				return;
			}
			rI-=b.getNumVertices();
		}

	}

	@Override
	public void display2D(PGraphics g) {
		// TODO Auto-generated method stub
		display2D(g, 10);
	}

	@Override
	public void display3D(PGraphics g) {
		// TODO Auto-generated method stub
		display3D(g, 10);
	}
}
